#include <cstdlib>
#include <cstdio>
#include <cstring>

#ifndef WIN32
# include <unistd.h>
# include <sys/socket.h>
# include <netdb.h>
# ifdef HAVE_NETINET_IN_H
#  include <netinet/in.h>
# endif // HAVE_NETINET_IN_H
# ifdef HAVE_ARPA_INET_H
#  include <arpa/inet.h>
# else // ! HAVE_ARPA_INET_H
#  define socklen_t int
# endif // ! HAVE_ARPA_INET_H
# define SOCKET int
#else // WIN32
# include "../win32/stdafx.h"
# include <winsock.h>
# include <io.h>
# define socklen_t int
# define close closesocket
# define read _read
# define write _write
#endif // WIN32

#include "GBA.h"
#include "GBAinline.h"
#include "GBAGlobals.h"
#include "../common/SystemGlobals.h"

extern bool debugger;
extern void CPUUpdateCPSR();
#ifdef SDL
extern void (*dbgMain)();
extern void debuggerMain();
extern void debuggerSignal(int, int);
#endif

int	   remotePort		  = 55555;
int	   remoteSignal		  = 5;
SOCKET remoteSocket		  = -1;
SOCKET remoteListenSocket = -1;
bool   remoteConnected	  = false;
bool   remoteResumed	  = false;

int	 (*remoteSendFnc)(char *, int) = NULL;
int	 (*remoteRecvFnc)(char *, int) = NULL;
bool (*remoteInitFnc)()	   = NULL;
void (*remoteCleanUpFnc)() = NULL;

#ifndef SDL
void remoteSetSockets(SOCKET l, SOCKET r)
{
	remoteSocket	   = r;
	remoteListenSocket = l;
}

#endif

int remoteTcpSend(char *data, int len)
{
	return send(remoteSocket, data, len, 0);
}

int remoteTcpRecv(char *data, int len)
{
	return recv(remoteSocket, data, len, 0);
}

bool remoteTcpInit()
{
	if (remoteSocket == -1)
	{
#ifdef WIN32
		WSADATA wsaData;
		int		error = WSAStartup(MAKEWORD(1, 1), &wsaData);
#endif // WIN32
		SOCKET s = socket(PF_INET, SOCK_STREAM, 0);

		remoteListenSocket = s;

		if (s == INVALID_SOCKET)
		{
			fprintf(stderr, "Error opening socket\n");
			exit(-1);
		}
		int tmp = 1;
		setsockopt(s, SOL_SOCKET, SO_REUSEADDR, (char *) &tmp, sizeof(tmp));

		//    char hostname[256];
		//    gethostname(hostname, 256);

		//    hostent *ent = gethostbyname(hostname);
		//    unsigned long a = *((unsigned long *)ent->h_addr);

		sockaddr_in addr;
		addr.sin_family		 = AF_INET;
		addr.sin_port		 = htons(remotePort);
		addr.sin_addr.s_addr = htonl(0);
		int count = 0;
		while (count < 3)
		{
			if (bind(s, (sockaddr *)&addr, sizeof(addr)))
			{
				addr.sin_port = htons(ntohs(addr.sin_port) + 1);
			}
			else
				break;
		}
		if (count == 3)
		{
			fprintf(stderr, "Error binding \n");
			exit(-1);
		}

		fprintf(stderr, "Listening for a connection at port %d\n",
		        ntohs(addr.sin_port));

		if (listen(s, 1))
		{
			fprintf(stderr, "Error listening\n");
			exit(-1);
		}
		socklen_t len = sizeof(addr);

#ifdef WIN32
		int flag = 0;
		ioctlsocket(s, FIONBIO, (unsigned long *)&flag);
#endif // WIN32
		SOCKET s2 = accept(s, (sockaddr *)&addr, &len);
		if (s2 > 0)
		{
			fprintf(stderr, "Got a connection from %s %d\n",
			        inet_ntoa((in_addr)addr.sin_addr),
			        ntohs(addr.sin_port));
		}
		else
		{
#ifdef WIN32
			int error = WSAGetLastError();
#endif // WIN32
		}
		//char dummy;
		//recv(s2, &dummy, 1, 0);
		//if(dummy != '+') {
		//  fprintf(stderr, "ACK not received\n");
		//  exit(-1);
		//}
		remoteSocket = s2;
		//    close(s);
	}
	return true;
}

void remoteTcpCleanUp()
{
	if (remoteSocket > 0)
	{
		fprintf(stderr, "Closing remote socket\n");
		close(remoteSocket);
		remoteSocket = -1;
	}
	if (remoteListenSocket > 0)
	{
		fprintf(stderr, "Closing listen socket\n");
		close(remoteListenSocket);
		remoteListenSocket = -1;
	}
}

int remotePipeSend(char *data, int len)
{
	int res = write(1, data, len);
	return res;
}

int remotePipeRecv(char *data, int len)
{
	int res = read(0, data, len);
	return res;
}

bool remotePipeInit()
{
//  char dummy;
//  if (read(0, &dummy, 1) == 1)
//  {
//    if(dummy != '+') {
//      fprintf(stderr, "ACK not received\n");
//      exit(-1);
//    }
//  }

	return true;
}

void remotePipeCleanUp()
{}

void remoteSetPort(int port)
{
	remotePort = port;
}

void remoteSetProtocol(int p)
{
	if (p == 0)
	{
		remoteSendFnc	 = remoteTcpSend;
		remoteRecvFnc	 = remoteTcpRecv;
		remoteInitFnc	 = remoteTcpInit;
		remoteCleanUpFnc = remoteTcpCleanUp;
	}
	else
	{
		remoteSendFnc	 = remotePipeSend;
		remoteRecvFnc	 = remotePipeRecv;
		remoteInitFnc	 = remotePipeInit;
		remoteCleanUpFnc = remotePipeCleanUp;
	}
}

void remoteInit()
{
	if (remoteInitFnc)
		remoteInitFnc();
}

void remotePutPacket(const char *packet)
{
	const char *hex = "0123456789abcdef";
	char		buffer[1024];

	size_t count = strlen(packet);

	unsigned char csum = 0;

	char *p = buffer;
	*p++ = '$';

	for (size_t i = 0; i < count; i++)
	{
		csum += packet[i];
		*p++  = packet[i];
	}
	*p++ = '#';
	*p++ = hex[csum >> 4];
	*p++ = hex[csum & 15];
	*p++ = 0;
	// printf("Sending %s\n", buffer);

	char c = 0;
	while (c != '+')
	{
		remoteSendFnc(buffer, (int)count + 4);
		remoteRecvFnc(&c, 1);
//    fprintf(stderr,"sent:%s recieved:%c\n",buffer,c);
	}
}

void remoteOutput(const char *s, u32 addr)
{
	char buffer[16384];

	char *d = buffer;
	*d++ = 'O';

	if (s)
	{
		char c = *s++;
		while (c)
		{
			sprintf(d, "%02x", c);
			d += 2;
			c  = *s++;
		}
	}
	else
	{
		char c = CPUReadByteQuick(addr);
		addr++;
		while (c)
		{
			sprintf(d, "%02x", c);
			d += 2;
			c  = CPUReadByteQuick(addr);
			addr++;
		}
	}
	remotePutPacket(buffer);
	//  fprintf(stderr, "Output sent %s\n", buffer);
}

void remoteSendSignal()
{
	char buffer[1024];
	sprintf(buffer, "S%02x", remoteSignal);
	remotePutPacket(buffer);
}

void remoteSendStatus()
{
	char buffer[1024];
	sprintf(buffer, "T%02x", remoteSignal);
	char *s = buffer;
	s += 3;
	for (int i = 0; i < 15; i++)
	{
		u32 v = reg[i].I;
		sprintf(s, "%02x:%02x%02x%02x%02x;", i,
		        (v & 255),
		        (v >> 8) & 255,
		        (v >> 16) & 255,
		        (v >> 24) & 255);
		s += 12;
	}
	u32 v = armNextPC;
	sprintf(s, "0f:%02x%02x%02x%02x;", (v & 255),
	        (v >> 8) & 255,
	        (v >> 16) & 255,
	        (v >> 24) & 255);
	s += 12;
	CPUUpdateCPSR();
	v = reg[16].I;
	sprintf(s, "19:%02x%02x%02x%02x;", (v & 255),
	        (v >> 8) & 255,
	        (v >> 16) & 255,
	        (v >> 24) & 255);
	s += 12;
	*s = 0;
	//  printf("Sending %s\n", buffer);
	remotePutPacket(buffer);
}

void remoteBinaryWrite(char *p)
{
	u32 address;
	int count;
	sscanf(p, "%x,%x:", &address, &count);
	//  printf("Binary write for %08x %d\n", address, count);

	p = strchr(p, ':');
	p++;
	for (int i = 0; i < count; i++)
	{
		u8 b = *p++;
		switch (b)
		{
		case 0x7d:
			b = *p++;
			CPUWriteByteQuick(address, (b ^ 0x20));
			address++;
			break;
		default:
			CPUWriteByteQuick(address, b);
			address++;
			break;
		}
	}
	//  printf("ROM is %08x\n", debuggerReadMemory(0x8000254));
	remotePutPacket("OK");
}

void remoteMemoryWrite(char *p)
{
	u32 address;
	int count;
	sscanf(p, "%x,%x:", &address, &count);
	//  printf("Memory write for %08x %d\n", address, count);

	p = strchr(p, ':');
	p++;
	for (int i = 0; i < count; i++)
	{
		u8	 v = 0;
		char c = *p++;
		if (c <= '9')
			v = (c - '0') << 4;
		else
			v = (c + 10 - 'a') << 4;
		c = *p++;
		if (c <= '9')
			v += (c - '0');
		else
			v += (c + 10 - 'a');
		CPUWriteByteQuick(address, v);
		address++;
	}
	//  printf("ROM is %08x\n", debuggerReadMemory(0x8000254));
	remotePutPacket("OK");
}

void remoteMemoryRead(char *p)
{
	u32 address;
	int count;
	sscanf(p, "%x,%x:", &address, &count);
	//  printf("Memory read for %08x %d\n", address, count);

	char buffer[1024];

	char *s = buffer;
	for (int i = 0; i < count; i++)
	{
		u8 b = CPUReadByteQuick(address);
		sprintf(s, "%02x", b);
		address++;
		s += 2;
	}
	*s = 0;
	remotePutPacket(buffer);
}

void remoteStepOverRange(char *p)
{
	u32 address;
	u32 final;
	sscanf(p, "%x,%x", &address, &final);

	remotePutPacket("OK");

	remoteResumed = true;
	do
	{
		CPULoop(1);
		if (debugger)
			break;
	}
	while (armNextPC >= address && armNextPC < final);

	remoteResumed = false;

	remoteSendStatus();
}

void remoteWriteWatch(char *p, bool active)
{
	u32 address;
	int count;
	sscanf(p, ",%x,%x#", &address, &count);

	fprintf(stderr, "Write watch for %08x %d\n", address, count);

	if (address < 0x2000000 || address > 0x3007fff)
	{
		remotePutPacket("E01");
		return;
	}

	if (address > 0x203ffff && address < 0x3000000)
	{
		remotePutPacket("E01");
		return;
	}

	u32 final = address + count;

	if (address < 0x2040000 && final > 0x2040000)
	{
		remotePutPacket("E01");
		return;
	}
	else if (address < 0x3008000 && final > 0x3008000)
	{
		remotePutPacket("E01");
		return;
	}

#ifdef BKPT_SUPPORT
	for (int i = 0; i < count; i++)
	{
		if ((address >> 24) == 2)
			freezeWorkRAM[address & 0x3ffff] = active;
		else
			freezeInternalRAM[address & 0x7fff] = active;
		address++;
	}
#endif

	remotePutPacket("OK");
}

void remoteReadRegisters(char *p)
{
	char buffer[1024];

	char *s = buffer;
	int	  i;
	// regular registers
	for (i = 0; i < 15; i++)
	{
		u32 v = reg[i].I;
		sprintf(s, "%02x%02x%02x%02x",  v & 255, (v >> 8) & 255,
		        (v >> 16) & 255, (v >> 24) & 255);
		s += 8;
	}
	// PC
	u32 pc = armNextPC;
	sprintf(s, "%02x%02x%02x%02x", pc & 255, (pc >> 8) & 255,
	        (pc >> 16) & 255, (pc >> 24) & 255);
	s += 8;

	// floating point registers (24-bit)
	for (i = 0; i < 8; i++)
	{
		sprintf(s, "000000000000000000000000");
		s += 24;
	}

	// FP status register
	sprintf(s, "00000000");
	s += 8;
	// CPSR
	CPUUpdateCPSR();
	u32 v = reg[16].I;
	sprintf(s, "%02x%02x%02x%02x",  v & 255, (v >> 8) & 255,
	        (v >> 16) & 255, (v >> 24) & 255);
	s += 8;
	*s = 0;
	remotePutPacket(buffer);
}

void remoteWriteRegister(char *p)
{
	int r;

	sscanf(p, "%x=", &r);

	p = strchr(p, '=');
	p++;

	char c = *p++;

	u32 v = 0;

	u8 data[4] = { 0, 0, 0, 0 };

	int i = 0;

	while (c != '#')
	{
		u8 b = 0;
		if (c <= '9')
			b = (c - '0') << 4;
		else
			b = (c + 10 - 'a') << 4;
		c = *p++;
		if (c <= '9')
			b += (c - '0');
		else
			b += (c + 10 - 'a');
		data[i++] = b;
		c		  = *p++;
	}

	v = data[0] | (data[1] << 8) | (data[2] << 16) | (data[3] << 24);

	//  printf("Write register %d=%08x\n", r, v);
	reg[r].I = v;
	if (r == 15)
	{
		armNextPC = v;
		if (armState)
			reg[15].I = v + 4;
		else
			reg[15].I = v + 2;
	}
	remotePutPacket("OK");
}

void remoteStubMain()
{
	if (!debugger)
		return;

	if (remoteResumed)
	{
		remoteSendStatus();
		remoteResumed = false;
	}

	const char *hex = "0123456789abcdef";
	while (true)
	{
		char ack;
		char buffer[1024];
		int	 res = remoteRecvFnc(buffer, 1024);

		if (res == -1)
		{
			fprintf(stderr, "GDB connection lost\n");
#ifdef SDL
			dbgMain	  = debuggerMain;
			dbgSignal = debuggerSignal;
#endif
			debugger = false;
			break;
		}
		if (res < 1024)
		{
			buffer[res] = 0;
		}
		else
		{
			fprintf(stderr, "res=%d\n", res);
		}

//    fprintf(stderr, "res=%d Received %s\n",res, buffer);
		char  c = buffer[0];
		char *p = &buffer[0];
		int	  i = 0;
		unsigned char csum = 0;
		while (i < res)
		{
			if (buffer[i] == '$')
			{
				i++;
				csum = 0;
				c	 = buffer[i];
				p	 = &buffer[i + 1];
				while ((i < res) && (buffer[i] != '#'))
				{
					csum += buffer[i];
					i++;
				}
			}
			else if (buffer[i] == '#')
			{
				buffer[i] = 0;
				if ((i + 2) < res)
				{
					if ((buffer[i + 1] == hex[csum >> 4]) && (buffer[i + 2] == hex[csum & 0xf]))
					{
						ack = '+';
						remoteSendFnc(&ack, 1);
						//fprintf(stderr, "SentACK c=%c\n",c);
						//process message...
						switch (c)
						{
						case '?':
							remoteSendSignal();
							break;
						case 'D':
							remotePutPacket("OK");
#ifdef SDL
							dbgMain	  = debuggerMain;
							dbgSignal = debuggerSignal;
#endif
							remoteResumed = true;
							debugger	  = false;
							return;
						case 'e':
							remoteStepOverRange(p);
							break;
						case 'k':
							remotePutPacket("OK");
#ifdef SDL
							dbgMain	  = debuggerMain;
							dbgSignal = debuggerSignal;
#endif
							debugger  = false;
							emulating = false;
							return;
						case 'C':
							remoteResumed = true;
							debugger	  = false;
							return;
						case 'c':
							remoteResumed = true;
							debugger	  = false;
							return;
						case 's':
							remoteResumed = true;
							remoteSignal  = 5;
							CPULoop(1);
							if (remoteResumed)
							{
								remoteResumed = false;
								remoteSendStatus();
							}
							break;
						case 'g':
							remoteReadRegisters(p);
							break;
						case 'P':
							remoteWriteRegister(p);
							break;
						case 'M':
							remoteMemoryWrite(p);
							break;
						case 'm':
							remoteMemoryRead(p);
							break;
						case 'X':
							remoteBinaryWrite(p);
							break;
						case 'H':
							remotePutPacket("OK");
							break;
						case 'q':
							remotePutPacket("");
							break;
						case 'Z':
							if (*p++ == '2')
							{
								remoteWriteWatch(p, true);
							}
							else
								remotePutPacket("");
							break;
						case 'z':
							if (*p++ == '2')
							{
								remoteWriteWatch(p, false);
							}
							else
								remotePutPacket("");
							break;
						default:
						{
							fprintf(stderr, "Unknown packet %s\n", --p);
							remotePutPacket("");
						}
						break;
						}
					}
					else
					{
						fprintf(stderr, "bad chksum csum=%x msg=%c%c\n", csum, buffer[i + 1], buffer[i + 2]);
						ack = '-';
						remoteSendFnc(&ack, 1);
						fprintf(stderr, "SentNACK\n");
					} //if
					i += 3;
				}
				else
				{
					fprintf(stderr, "didn't receive chksum i=%d res=%d\n", i, res);
					i++;
				} //if
			}
			else
			{
				if (buffer[i] != '+') //ingnore ACKs
				{
					fprintf(stderr, "not sure what to do with:%c i=%d res=%d\n", buffer[i], i, res);
				}
				i++;
			} //if
		} //while
	}
}

void remoteStubSignal(int sig, int number)
{
	remoteSignal  = sig;
	remoteResumed = false;
	remoteSendStatus();
	debugger = true;
}

void remoteCleanUp()
{
	if (remoteCleanUpFnc)
		remoteCleanUpFnc();
}

